﻿using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using Emotiv;

namespace DasherBci
{
    public delegate void NewEegDataEventHandler(object sender, EegDataEventArgs e);

    public class EmotivController
    {
        private readonly EmoEngine _engine = EmoEngine.Instance;
        private TrainingResult _trainingResult;
        public event NewEegDataEventHandler NewEegData;

        private const uint ActionsBitVector = (uint) EdkDll.EE_CognitivAction_t.COG_DROP | (uint) EdkDll.EE_CognitivAction_t.COG_LIFT;
        private BackgroundWorker _worker;

        ~EmotivController()
        {
            Disconnect();
        }

        public void Connect()
        {
            _engine.Connect();
            SetOptimisations();
        }

        private void SetOptimisations()
        {
            _engine.OptimizationDisable();
            var optimisations = new OptimizationParam();
            optimisations.SetVitalAlgorithm(EdkDll.EE_EmotivSuite_t.EE_COGNITIV, ActionsBitVector | (uint) EdkDll.EE_CognitivAction_t.COG_NEUTRAL);
            _engine.OptimizationEnable(optimisations);
        }

        public void Disconnect()
        {
            _engine.Disconnect();
        }

        public void SaveProfile(string filepath)
        {
            _engine.EE_SaveUserProfile(CurrentUserId, filepath);
        }

        public void LoadProfile(string filepath)
        {
            _engine.LoadUserProfile(CurrentUserId, filepath);
        }

        public int ConnectedUsersCount
        {
            get { return (int)_engine.EngineGetNumUser(); }
        }

        private uint CurrentUserId
        {
            get
            {
                if (ConnectedUsersCount > 0) return (uint) ConnectedUsersCount - 1;
                throw new DasherBciException(ErrorCode.NoConnectedUsers);
            }
        }

        public TrainingResult AttemptTraining(TrainedAction actionToTrain)
        {
            _engine.CognitivSetActiveActions(CurrentUserId, ActionsBitVector);
            _trainingResult = TrainingResult.Unset;
            _engine.CognitivTrainingFailed += _engine_CognitivTrainingFailed;
            _engine.CognitivTrainingSucceeded += _engine_CognitivTrainingSucceeded;

            _engine.CognitivSetTrainingAction(CurrentUserId, TrainedActionCognitivActionMapping.GetCognitivAction(actionToTrain));
            _engine.CognitivSetTrainingControl(CurrentUserId, EdkDll.EE_CognitivTrainingControl_t.COG_START);

            // Loop until training either succeeds or fails
            while (_trainingResult == TrainingResult.Unset) { _engine.ProcessEvents(); }

            _engine.CognitivTrainingFailed -= _engine_CognitivTrainingFailed;
            _engine.CognitivTrainingSucceeded -= _engine_CognitivTrainingSucceeded;

            return _trainingResult;
        }

        private void _engine_CognitivTrainingSucceeded(object sender, EmoEngineEventArgs e)
        {
            _trainingResult = TrainingResult.Success;
            _engine.CognitivSetTrainingControl(CurrentUserId, EdkDll.EE_CognitivTrainingControl_t.COG_ACCEPT);
        }

        private void _engine_CognitivTrainingFailed(object sender, EmoEngineEventArgs e)
        {
            _trainingResult = TrainingResult.Failure;
        }

        public void BeginReadingData()
        {
            _engine.CognitivSetActiveActions(CurrentUserId, ActionsBitVector);
            _engine.CognitivEmoStateUpdated += engine_CognitivEmoStateUpdated;

            _worker = new BackgroundWorker {WorkerSupportsCancellation = true};
            _worker.DoWork += _worker_DoWork;
            _worker.RunWorkerAsync();

            _engine.DataAcquisitionEnable(CurrentUserId, true);
        }

        private void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
            var worker = (BackgroundWorker) sender;
            while (!worker.CancellationPending)
            {
                _engine.ProcessEvents();
            }
        }

        public void EndReadingData()
        {
            _engine.CognitivEmoStateUpdated -= engine_CognitivEmoStateUpdated;
            _engine.DataAcquisitionEnable(CurrentUserId, false);
            _worker.CancelAsync();
        }

        private void engine_CognitivEmoStateUpdated(object sender, EmoStateUpdatedEventArgs e)
        {
            EdkDll.EE_CognitivAction_t cognitivAction = e.emoState.CognitivGetCurrentAction();
            TrainedAction action = TrainedActionCognitivActionMapping.GetTrainedAction(cognitivAction);
            double actionPower = e.emoState.CognitivGetCurrentActionPower();
            var eventArgs = new EegDataEventArgs(action, actionPower);
            if (NewEegData != null) NewEegData(this, eventArgs);
        }
    }
}
